feat: consumer installer with per-skill lockfile and managed delivery - #204
feat: consumer installer with per-skill lockfile and managed delivery#204LadyBluenotes wants to merge 60 commits into
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
📝 WalkthroughWalkthroughThe PR adds lockfile-backed skill integrity checks, cached session catalogues and hooks, interactive installation and synchronization workflows, managed link state, strict configuration handling, cross-platform delivery tests, and extensive unit, integration, and benchmark coverage. ChangesIntent workflows
Estimated code review effort: 5 (Critical) | ~120 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
|
Review the following changes in direct dependencies. Learn more about Socket for GitHub.
|
|
Warning Review the following alerts detected in dependencies. According to your organization's Security Policy, it is recommended to resolve "Warn" alerts. Learn more about Socket for GitHub.
|
|
View your CI Pipeline Execution ↗ for commit 1746da4
☁️ Nx Cloud last updated this comment at |
commit: |
There was a problem hiding this comment.
Actionable comments posted: 6
🧹 Nitpick comments (17)
packages/intent/src/core/lockfile/lockfile-state.ts (1)
17-52: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDocument the suffix-matching heuristic in
packageRelativeSkillFile.The
forloop that walkspackageSegmentslooking for the longest suffix matching a prefix ofskillSegmentsis correct for the tested shapes (barenode_modules/<pkg>/...and full workspace-relative paths) but is non-obvious. Since this function determines the canonical skill path baked intointent.lock(the security-relevant acceptance record), a short comment explaining why it scans fromstart=0upward (to prefer the longest/most-specific suffix match) would reduce the risk of a subtly wrong edit later.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/core/lockfile/lockfile-state.ts` around lines 17 - 52, Document the suffix-matching heuristic in packageRelativeSkillFile immediately above the loop: explain that scanning packageSegments from start=0 upward checks progressively shorter suffixes while preferring the longest, most-specific suffix matching the skillSegments prefix, preserving canonical lockfile path resolution.packages/intent/src/core/lockfile/lockfile.ts (1)
156-167: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
readIntentLockfilebypasses the injectable-fs pattern used elsewhere in this PR.Unlike
computeSkillContentHash,buildCurrentLockfileSources, andscanIntentPackageAtRoot, which all accept aReadFs/fsCache,readIntentLockfilecallsreadFileSyncfromnode:fsdirectly. Its two call sites —intent-core.ts'stoResolvedIntentSkillandcatalog-lock.ts'sapplyCatalogueLock— both otherwise thread an injected/cachedreadFsthrough the rest of the same function, so this is the one lockfile read that always hits real disk regardless of caching. Consider accepting an optionalfs/readFsparameter (defaulting to realreadFileSync) for consistency and to avoid an uncached disk read + JSON parse on every skill resolution.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/core/lockfile/lockfile.ts` around lines 156 - 167, Update readIntentLockfile to accept the injected ReadFs/fsCache reader used by computeSkillContentHash, buildCurrentLockfileSources, and scanIntentPackageAtRoot, while defaulting to the real filesystem reader for existing callers. Thread the available readFs through intent-core.ts’s toResolvedIntentSkill and catalog-lock.ts’s applyCatalogueLock so lockfile reads use the same cache instead of direct readFileSync.packages/intent/tests/core.test.ts (1)
541-571: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick winAdd a
resolveIntentSkillassertion for parity with the sibling tests.The two adjacent tests (content-hash mismatch, missing lock entry) both assert
loadIntentSkillandresolveIntentSkillthrow identically. This test only checksloadIntentSkill, leaving the wrong-source-kind path unverified forresolveIntentSkill, even though both share the sametoResolvedIntentSkillcheck.✅ Proposed addition
expect(() => loadIntentSkill('`@tanstack/query`#fetching', { cwd: root }), ).toThrow( 'Cannot load skill use "`@tanstack/query`#fetching": skill is not accepted in intent.lock.', ) + expect(() => + resolveIntentSkill('`@tanstack/query`#fetching', { cwd: root }), + ).toThrow( + 'Cannot load skill use "`@tanstack/query`#fetching": skill is not accepted in intent.lock.', + ) })🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/tests/core.test.ts` around lines 541 - 571, Extend the wrong-source-kind test to also assert that resolveIntentSkill rejects the same `@tanstack/query`#fetching request with the identical intent.lock error, matching the adjacent content-hash and missing-entry tests. Keep the existing loadIntentSkill assertion unchanged and reuse the same root and fixture setup.packages/intent/src/discovery/scanner.ts (1)
169-171: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winMemoize per root — each call re-executes the PnP runtime.
loadPnpApirequires and runs.pnp.cjssetup(), which patches the globalfsmodule process-wide.getProjectReadFsis invoked at least once fromgetIntentCatalogContextand again as the default argument inapplyCatalogueLock, so a singleintent catalogrun can load and set up the PnP runtime twice. The scanner already caches this lazily via its internalpnpApivariable; a small module-levelMap<string, ReadFs>here would give the same guarantee to external callers.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/discovery/scanner.ts` around lines 169 - 171, Memoize getProjectReadFs results per root to avoid repeated PnP runtime setup. Add a module-level Map<string, ReadFs>, return the cached filesystem when available, and cache either the loaded PnP readFs or nodeReadFs before returning from getProjectReadFs.packages/intent/src/core/source-policy.ts (1)
199-207: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winDiscovery warnings are dropped here.
scanForIntentsreturnswarnings(invalidintentfields, version conflicts) whichscanForConfiguredIntentsdiscards, sointent synccan silently ignore real discovery problems while only surfacingpolicy.notices. Consider returningwarningsalongsidediscovered/policyso the sync output can include them.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/core/source-policy.ts` around lines 199 - 207, Update scanForConfiguredIntents to preserve and return the warnings from scanForIntents alongside discovered and policy. Ensure the intent sync output can consume these discovery warnings in addition to policy.notices, without filtering or replacing the existing warning details.packages/intent/tests/cli.test.ts (1)
291-338: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winClear
logSpybetween lifecycle phases.Each assertion joins the cumulative call buffer, so output emitted by an earlier
main(['sync'])in this test keeps satisfying latertoContainchecks. If a phase stops printing its block, the test can still pass. AddlogSpy.mockClear()immediately before eachmain(['sync'])whose output you then assert.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/tests/cli.test.ts` around lines 291 - 338, Clear the cumulative log buffer before each lifecycle phase in the test’s sync flow: call logSpy.mockClear() immediately before every main(['sync']) invocation whose output is subsequently asserted. Keep the existing assertions and sync behavior unchanged while ensuring each check validates only that phase’s log output.packages/intent/src/session-catalog.ts (2)
310-317: 🩺 Stability & Availability | 🔵 Trivial | ⚡ Quick winBound the upward walk.
while (directory !== workspaceRoot)relies on exact string equality to terminate; any normalization drift betweenpolicyRootandworkspaceRoot(trailing separator, case on Windows) turns this into an infinite loop, sincedirnamebecomes a fixed point at the filesystem root. Add a depth/dirnamefixed-point guard.🛡️ Proposed guard
const manifests: Array<string> = [] let directory = policyRoot while (directory !== workspaceRoot) { manifests.push(join(directory, 'package.json')) - directory = dirname(directory) + const parent = dirname(directory) + if (parent === directory) break + directory = parent }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/session-catalog.ts` around lines 310 - 317, Bound the upward manifest walk by updating the loop around directory and workspaceRoot to stop when the current directory reaches workspaceRoot or dirname(directory) becomes unchanged at the filesystem root. Preserve collecting package.json for each traversed directory and avoid adding entries indefinitely when path normalization differs.
249-266: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick winFingerprint reads full lockfile contents on every hook invocation.
hash.update(readFileSync(file))slurps every lockfile (pnpm-lock.yaml,package-lock.json, …) plus every workspacepackage.jsonon eachgetSessionCataloguecall, including cache hits. In a large monorepo that is tens of MB of I/O inside a session-start hook that has a 10s timeout. Hashingsize+mtimeMsfromstatSync(falling back to content only when needed) would give the same invalidation signal far more cheaply.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/session-catalog.ts` around lines 249 - 266, Update the fingerprinting loop in the session-catalog fingerprint function to avoid reading full lockfiles and workspace package.json files on every invocation. Use each file’s stat metadata, including size and modification time, as the primary hash input, and fall back to hashing file contents only when stat metadata cannot be obtained; preserve the existing missing-file handling and deterministic path ordering.packages/intent/tests/catalog-api.test.ts (1)
72-77: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winTests leak session-cache files into the OS temp dir.
getIntentCatalogContextwrites to the defaultos.tmpdir()/tanstack-intent/cataloguesandafterEachonly removes the fixture roots, so every run leaves orphaned cache JSON behind. Threading acacheDiroption throughgetIntentCatalogContext(already supported bygetSessionCatalogue) would let these tests point at a fixture-local directory and clean up deterministically.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/tests/catalog-api.test.ts` around lines 72 - 77, Update the test setup around getIntentCatalogContext to provide a fixture-local cacheDir, using the existing roots-based temporary directory rather than the default OS temp location. Ensure the option is threaded through the context creation to getSessionCatalogue, and retain the afterEach cleanup so the cache files are removed with the fixture roots.packages/intent/tests/lockfile.test.ts (1)
73-103: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAssert on the rejection reason, not just that something throws.
All seven cases use bare
toThrow(), so a parse failure for an unrelated reason (or a future message change that collapses two distinct validations into one) still passes. Since this is the fail-closed path for lockfile integrity, match the expected messages, e.g.toThrow(/source kind/)for the"kind":"git"case and/duplicate/ifor the duplicate id/path cases.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/tests/lockfile.test.ts` around lines 73 - 103, Update the rejection assertions in the lockfile validation test to match each case’s expected error message, rather than using bare toThrow() calls. Anchor the expectations to the relevant validation symbols and messages: unknown top-level fields, unsupported lockfileVersion, invalid sources type, invalid source kind, duplicate source identity, duplicate skill paths, and traversal paths should each assert a targeted message; use patterns such as /source kind/ and /duplicate/i where appropriate.packages/intent/src/hooks/install.ts (2)
428-437: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low valueRename
isIntentGateScriptReferencenow that it matches catalog scripts too.The predicate no longer identifies only gate scripts;
isIntentHookScriptReferencereflects the widened regex.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/hooks/install.ts` around lines 428 - 437, Rename isIntentGateScriptReference to isIntentHookScriptReference throughout its declaration and all call sites, preserving the existing regex and behavior that matches both gate and catalog scripts.
354-354: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winAvoid materializing an empty
PreToolUsekey in agent configs.
removeIntentHooks(arrayValue(hooks.PreToolUse))always assigns an array, so a config that never hadPreToolUsegains"PreToolUse": [](and a user whose only Intent hook is removed is left with an empty array). Drop the key when the result is empty to keep the written config clean.♻️ Proposed cleanup helper
- hooks.PreToolUse = removeIntentHooks(arrayValue(hooks.PreToolUse)) + const preToolUse = removeIntentHooks(arrayValue(hooks.PreToolUse)) + if (preToolUse.length > 0) hooks.PreToolUse = preToolUse + else delete hooks.PreToolUseAlso applies to: 377-377, 389-390
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/hooks/install.ts` at line 354, Update the hook cleanup assignments around hooks.PreToolUse so empty results do not create or retain a PreToolUse key; only assign the filtered array when non-empty, otherwise remove the property. Apply the same behavior to the additional affected hook-processing paths while preserving existing handling for non-empty hooks.packages/intent/src/commands/sync/gitignore.ts (1)
12-15: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low valueHoist the block matcher to a module constant.
START/ENDare static, so the escaping andRegExpconstruction can be computed once instead of on every call. (The ReDoS hint from static analysis is a false positive here — the pattern derives from constants and is escaped.)🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/commands/sync/gitignore.ts` around lines 12 - 15, Hoist the escaped START/END block matcher from the current function into a module-level constant, preserving the existing non-greedy pattern and replacement behavior in the sync logic. Remove the per-call RegExp construction while continuing to use the shared matcher for prefix replacement.Source: Linters/SAST tools
packages/intent/src/commands/sync/command.ts (1)
90-97: 🗄️ Data Integrity & Integration | 🔵 Trivial | ⚡ Quick winUse
writeTextFileAtomicfor the.gitignoreupdate.Every other managed file in this module (
package.json, install state) is written atomically; a plainwriteFileSynchere can truncate a user's.gitignoreif the process dies mid-write.♻️ Proposed fix
- writeFileSync(path, after, 'utf8') + writeTextFileAtomic(path, after) return true🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/commands/sync/command.ts` around lines 90 - 97, Update writeGitignore to replace the direct writeFileSync call with the module’s existing writeTextFileAtomic helper, preserving the current before/after comparison and boolean return behavior.packages/intent/src/commands/sync/prepare.ts (2)
16-20: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low valueIdempotency only holds for
&&-chained scripts.A
preparevalue like"build; intent sync"or"pnpm exec intent sync"won't be detected, so a second wiring appends a duplicate&& intent sync. Splitting on[;&]+(and optionally allowing a runner prefix) would cover the common shapes.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/commands/sync/prepare.ts` around lines 16 - 20, Update containsIntentSync to recognize intent sync commands separated by both && and semicolons, and allow common runner prefixes such as pnpm exec before intent sync. Preserve whitespace handling and ensure these command forms are detected so repeated wiring remains idempotent.
33-42: 🗄️ Data Integrity & Integration | 🔵 Trivial | 💤 Low valueNon-string existing
prepareis silently overwritten.If
scripts.prepareis present but not a string (malformed manifest), the edit replaces it with"intent sync"without warning. Consider failing loudly like the parse-error path.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/src/commands/sync/prepare.ts` around lines 33 - 42, Update the prepare-edit logic around scripts.prepare to detect a present non-string value and fail loudly using the same error behavior as the parse-error path, rather than replacing it with "intent sync". Preserve the existing handling for missing, string, and intent-sync-containing prepare values.packages/intent/package.json (1)
20-22: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winPut
typesbeforeimportin the new./catalogcondition block.Conditional exports are matched in declaration order; TypeScript's
node16/bundlerresolvers can matchimportfirst and only findcatalog.d.mtsvia sibling-name fallback. Orderingtypesfirst makes declaration resolution explicit and consistent with the other subpaths.♻️ Proposed ordering fix
"./catalog": { - "import": "./dist/catalog.mjs", - "types": "./dist/catalog.d.mts" + "types": "./dist/catalog.d.mts", + "import": "./dist/catalog.mjs" }🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/package.json` around lines 20 - 22, Reorder the conditions in the "./catalog" export block so the existing "types" entry appears before "import", matching the ordering used by other subpaths and ensuring declaration resolution selects catalog.d.mts explicitly.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@packages/intent/src/commands/sync/links.ts`:
- Around line 96-104: Update removeLink to report removal failure as a
non-throwing result, allowing reconcileManagedLinks callers at the removal sites
to add entry.path to conflicts when unlinkSync and rmdirSync both fail. Preserve
successful handling for file links and directory symlinks, and ensure
reconciliation continues so install state can still be written.
In `@packages/intent/src/commands/sync/prompts.ts`:
- Around line 1-27: Update createClackSyncReviewPrompter.reviewNewDependencies
to use the caller-provided dependency summaries when constructing the prompt.
Surface each package name and its pending skillCount, matching the summary-style
feedback already used by selectClackSkills, while preserving the existing
decision options and cancellation behavior.
In `@packages/intent/src/commands/sync/state.ts`:
- Around line 37-62: Update parseEntry to reject path values that are not
project-relative POSIX paths by adding the existing isProjectRelativePath
validation to its rejection checks. Keep accepting valid string paths and
preserve the current InstallStateEntry mapping for all other validated fields.
In `@packages/intent/src/session-catalog.ts`:
- Around line 181-234: Harden getSessionCatalogue’s default cache location
against cross-user tampering by using a per-user root derived from
XDG_CACHE_HOME or os.homedir() instead of os.tmpdir(), creating the cache
directory with mode 0o700, and rejecting cache files not owned by the current
uid before readCache accepts them. Preserve explicit cacheDir behavior as
appropriate while ensuring cache hits cannot use foreign-owned files, including
entries with empty verification.
In `@packages/intent/src/shared/local-path.ts`:
- Around line 61-63: Update the local-path detection condition in the visible
path-classification logic to treat USER_DATA_POSIX_ROOTS paths with two or more
segments as local by changing the segments.length threshold. Add regression
cases covering /home/alice and /Users/alice, ensuring both are redacted while
preserving existing behavior for shorter paths.
In `@packages/intent/tests/sync.test.ts`:
- Around line 170-198: Update the symlink setup in the tests around “does not
replace unmanaged links and makes dry runs non-writing” and “treats an
unreadable owned link target as a conflict” to use the existing platform-aware
makeLink helper instead of raw symlinkSync(..., 'dir') calls. Preserve the
unmanaged-target and nonexistent-target scenarios while ensuring directory links
use junctions on Windows.
---
Nitpick comments:
In `@packages/intent/package.json`:
- Around line 20-22: Reorder the conditions in the "./catalog" export block so
the existing "types" entry appears before "import", matching the ordering used
by other subpaths and ensuring declaration resolution selects catalog.d.mts
explicitly.
In `@packages/intent/src/commands/sync/command.ts`:
- Around line 90-97: Update writeGitignore to replace the direct writeFileSync
call with the module’s existing writeTextFileAtomic helper, preserving the
current before/after comparison and boolean return behavior.
In `@packages/intent/src/commands/sync/gitignore.ts`:
- Around line 12-15: Hoist the escaped START/END block matcher from the current
function into a module-level constant, preserving the existing non-greedy
pattern and replacement behavior in the sync logic. Remove the per-call RegExp
construction while continuing to use the shared matcher for prefix replacement.
In `@packages/intent/src/commands/sync/prepare.ts`:
- Around line 16-20: Update containsIntentSync to recognize intent sync commands
separated by both && and semicolons, and allow common runner prefixes such as
pnpm exec before intent sync. Preserve whitespace handling and ensure these
command forms are detected so repeated wiring remains idempotent.
- Around line 33-42: Update the prepare-edit logic around scripts.prepare to
detect a present non-string value and fail loudly using the same error behavior
as the parse-error path, rather than replacing it with "intent sync". Preserve
the existing handling for missing, string, and intent-sync-containing prepare
values.
In `@packages/intent/src/core/lockfile/lockfile-state.ts`:
- Around line 17-52: Document the suffix-matching heuristic in
packageRelativeSkillFile immediately above the loop: explain that scanning
packageSegments from start=0 upward checks progressively shorter suffixes while
preferring the longest, most-specific suffix matching the skillSegments prefix,
preserving canonical lockfile path resolution.
In `@packages/intent/src/core/lockfile/lockfile.ts`:
- Around line 156-167: Update readIntentLockfile to accept the injected
ReadFs/fsCache reader used by computeSkillContentHash,
buildCurrentLockfileSources, and scanIntentPackageAtRoot, while defaulting to
the real filesystem reader for existing callers. Thread the available readFs
through intent-core.ts’s toResolvedIntentSkill and catalog-lock.ts’s
applyCatalogueLock so lockfile reads use the same cache instead of direct
readFileSync.
In `@packages/intent/src/core/source-policy.ts`:
- Around line 199-207: Update scanForConfiguredIntents to preserve and return
the warnings from scanForIntents alongside discovered and policy. Ensure the
intent sync output can consume these discovery warnings in addition to
policy.notices, without filtering or replacing the existing warning details.
In `@packages/intent/src/discovery/scanner.ts`:
- Around line 169-171: Memoize getProjectReadFs results per root to avoid
repeated PnP runtime setup. Add a module-level Map<string, ReadFs>, return the
cached filesystem when available, and cache either the loaded PnP readFs or
nodeReadFs before returning from getProjectReadFs.
In `@packages/intent/src/hooks/install.ts`:
- Around line 428-437: Rename isIntentGateScriptReference to
isIntentHookScriptReference throughout its declaration and all call sites,
preserving the existing regex and behavior that matches both gate and catalog
scripts.
- Line 354: Update the hook cleanup assignments around hooks.PreToolUse so empty
results do not create or retain a PreToolUse key; only assign the filtered array
when non-empty, otherwise remove the property. Apply the same behavior to the
additional affected hook-processing paths while preserving existing handling for
non-empty hooks.
In `@packages/intent/src/session-catalog.ts`:
- Around line 310-317: Bound the upward manifest walk by updating the loop
around directory and workspaceRoot to stop when the current directory reaches
workspaceRoot or dirname(directory) becomes unchanged at the filesystem root.
Preserve collecting package.json for each traversed directory and avoid adding
entries indefinitely when path normalization differs.
- Around line 249-266: Update the fingerprinting loop in the session-catalog
fingerprint function to avoid reading full lockfiles and workspace package.json
files on every invocation. Use each file’s stat metadata, including size and
modification time, as the primary hash input, and fall back to hashing file
contents only when stat metadata cannot be obtained; preserve the existing
missing-file handling and deterministic path ordering.
In `@packages/intent/tests/catalog-api.test.ts`:
- Around line 72-77: Update the test setup around getIntentCatalogContext to
provide a fixture-local cacheDir, using the existing roots-based temporary
directory rather than the default OS temp location. Ensure the option is
threaded through the context creation to getSessionCatalogue, and retain the
afterEach cleanup so the cache files are removed with the fixture roots.
In `@packages/intent/tests/cli.test.ts`:
- Around line 291-338: Clear the cumulative log buffer before each lifecycle
phase in the test’s sync flow: call logSpy.mockClear() immediately before every
main(['sync']) invocation whose output is subsequently asserted. Keep the
existing assertions and sync behavior unchanged while ensuring each check
validates only that phase’s log output.
In `@packages/intent/tests/core.test.ts`:
- Around line 541-571: Extend the wrong-source-kind test to also assert that
resolveIntentSkill rejects the same `@tanstack/query`#fetching request with the
identical intent.lock error, matching the adjacent content-hash and
missing-entry tests. Keep the existing loadIntentSkill assertion unchanged and
reuse the same root and fixture setup.
In `@packages/intent/tests/lockfile.test.ts`:
- Around line 73-103: Update the rejection assertions in the lockfile validation
test to match each case’s expected error message, rather than using bare
toThrow() calls. Anchor the expectations to the relevant validation symbols and
messages: unknown top-level fields, unsupported lockfileVersion, invalid sources
type, invalid source kind, duplicate source identity, duplicate skill paths, and
traversal paths should each assert a targeted message; use patterns such as
/source kind/ and /duplicate/i where appropriate.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 76f89a70-96e1-47f3-87de-849146236d71
⛔ Files ignored due to path filters (1)
pnpm-lock.yamlis excluded by!**/pnpm-lock.yaml
📒 Files selected for processing (64)
.github/workflows/pr.ymlbenchmarks/intent/catalog.bench.tsbenchmarks/intent/install-plan.bench.tsbenchmarks/intent/lockfile-hash.bench.tsbenchmarks/intent/sync.bench.tsbenchmarks/intent/tsconfig.jsonknip.jsonpackages/intent/package.jsonpackages/intent/src/catalog-lock.tspackages/intent/src/catalog.tspackages/intent/src/cli.tspackages/intent/src/commands/catalog.tspackages/intent/src/commands/install/command.tspackages/intent/src/commands/install/config.tspackages/intent/src/commands/install/consumer.tspackages/intent/src/commands/install/plan.tspackages/intent/src/commands/install/prompts.tspackages/intent/src/commands/sync/command.tspackages/intent/src/commands/sync/gitignore.tspackages/intent/src/commands/sync/links.tspackages/intent/src/commands/sync/plan.tspackages/intent/src/commands/sync/prepare.tspackages/intent/src/commands/sync/prompts.tspackages/intent/src/commands/sync/state.tspackages/intent/src/commands/sync/targets.tspackages/intent/src/core/intent-core.tspackages/intent/src/core/load-resolution.tspackages/intent/src/core/lockfile/hash.tspackages/intent/src/core/lockfile/lockfile-diff.tspackages/intent/src/core/lockfile/lockfile-state.tspackages/intent/src/core/lockfile/lockfile.tspackages/intent/src/core/skill-path.tspackages/intent/src/core/source-policy.tspackages/intent/src/core/types.tspackages/intent/src/discovery/scanner.tspackages/intent/src/hooks/adapters.tspackages/intent/src/hooks/agents/claude.tspackages/intent/src/hooks/agents/codex.tspackages/intent/src/hooks/agents/copilot.tspackages/intent/src/hooks/install.tspackages/intent/src/hooks/policy.tspackages/intent/src/hooks/types.tspackages/intent/src/session-catalog.tspackages/intent/src/shared/atomic-write.tspackages/intent/src/shared/command-runner.tspackages/intent/src/shared/local-path.tspackages/intent/src/skills/resolver.tspackages/intent/tests/catalog-api.test.tspackages/intent/tests/cli.test.tspackages/intent/tests/consumer-install.test.tspackages/intent/tests/core.test.tspackages/intent/tests/hash.test.tspackages/intent/tests/hooks-install.test.tspackages/intent/tests/hooks.test.tspackages/intent/tests/install-config.test.tspackages/intent/tests/install-plan.test.tspackages/intent/tests/local-path.test.tspackages/intent/tests/lockfile-diff.test.tspackages/intent/tests/lockfile-state.test.tspackages/intent/tests/lockfile.test.tspackages/intent/tests/resolver.test.tspackages/intent/tests/session-catalog.test.tspackages/intent/tests/skill-path.test.tspackages/intent/tests/sync.test.ts
💤 Files with no reviewable changes (8)
- packages/intent/src/hooks/agents/copilot.ts
- packages/intent/src/shared/command-runner.ts
- benchmarks/intent/tsconfig.json
- packages/intent/src/hooks/agents/claude.ts
- packages/intent/src/hooks/agents/codex.ts
- packages/intent/tests/hooks.test.ts
- packages/intent/src/hooks/policy.ts
- packages/intent/src/hooks/types.ts
There was a problem hiding this comment.
🧹 Nitpick comments (1)
packages/intent/tests/integration/source-policy-surfaces.test.ts (1)
90-105: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick winAlso cover agent-facing warning and source redaction.
The result exposes
warningsandhiddenSourcesin addition topackagesandnotices. Assert thatUNLISTEDis absent from warnings and thathiddenSourcesis empty foraudience: 'agent'; otherwise a privacy regression could pass this test.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@packages/intent/tests/integration/source-policy-surfaces.test.ts` around lines 90 - 105, Extend the agent-audience test around listIntentSkills to assert that no warning contains UNLISTED and that result.hiddenSources is empty. Keep the existing package and notice assertions unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@packages/intent/tests/integration/source-policy-surfaces.test.ts`:
- Around line 90-105: Extend the agent-audience test around listIntentSkills to
assert that no warning contains UNLISTED and that result.hiddenSources is empty.
Keep the existing package and notice assertions unchanged.
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 1c2cba6f-62c8-470d-8ac7-43c41c7f3c8a
📒 Files selected for processing (7)
packages/intent/src/commands/exclude.tspackages/intent/src/commands/install/plan.tspackages/intent/src/core/lockfile/lockfile.tspackages/intent/tests/install-plan.test.tspackages/intent/tests/integration/source-policy-surfaces.test.tspackages/intent/tests/lockfile.test.tspackages/intent/tests/scanner.test.ts
🚧 Files skipped from review as they are similar to previous changes (3)
- packages/intent/tests/install-plan.test.ts
- packages/intent/src/commands/install/plan.ts
- packages/intent/src/core/lockfile/lockfile.ts
Hook configs and AGENTS.md blocks are written once and executed on every agent session, so `@latest` meant a file written today would silently run a future major version. Generated commands now carry the writing CLI's own version: an exact pin for prereleases, since npm ranges exclude them, and major.minor otherwise so committed files stay stable across patch releases. The AGENTS.md verifier accepts pinned specifiers alongside the older forms.
…m skill content hashing
🎯 Changes
intent installbecomes a real setup command. Today a consumer has to discover every skill-shipping dependency and hand-type each one intopackage.json#intent.skills; nothing in the CLI writes that file. This branch makes one confirmed command write the policy, the content baseline, the delivery preferences, and the delivery artifacts together.Four layers, one command
intent.skills/intent.exclude) records which sources may contribute.intent.lockrecords the content baseline that was accepted, hashed per skill.intent installwalks method → targets → skills → confirm, then writes once.intent syncreconciles delivery afterwards without prompting and without ever writingintent.lock, so it is safe to wire intoprepare.Per-skill lockfile
intent.lockhashes each skill directory individually —SKILL.mdplus supportedreferences/,assets/, andscripts/files. A new, changed, or removed skill therefore does not invalidate its unchanged accepted siblings from the same package.intent loadand catalogue generation both refuse content that no longer matches the accepted baseline.Managed symlinks
Skill folders are linked directly from their installed packages using the
skills-npmpackage-prefixed convention, so the source identity survives in the installed name and same-named skills from different packages cannot collide.Cleanup is the part that needed care. Intent removes a link only when persisted ownership state says it created it and the on-disk entry is still a link to the exact target recorded. A real directory, an unmanaged link, or a link that now points elsewhere is a conflict and stays untouched. The
npm-/workspace-prefix is a readability convention, never proof of ownership.Symlinks expose live package content, which the installer states plainly and requires explicit confirmation for. Intent detects drift on its next run; it cannot prevent a read in between. That is a real difference from
intent load, which checks the lock immediately before returning content.Removes the session-wide hook edit gate
One observed
intent loadcould not prove the loaded skill matched the current task, so the gate blocked edits without evidence. Reinstalling hooks clears old gate entries without touching unrelated user hooks.✅ Checklist
pnpm run test:pr.🚀 Release Impact
Changesets are deliberately deferred until the public surface stops moving. This box is not yet truthful — do not merge before it is.
Summary by CodeRabbit
intent catalogwith cached session guidance (--json,--refresh).intent syncfor verified link synchronization (--json, interactive review).intent installwith TTY-based interactive method/target/skill selection and improved dry-run previews.intent.lockverification to prevent stale or mismatched skills from being used..gitignoreupdates.